/*****************************************************************************
*           Change Log
*  Date     | Change
*-----------+-----------------------------------------------------------------
* 22-Mar-96 | Created
* 24-Jan-00 | REQ #137: Fixed string store to use length+1
* 20-Feb-01 | REQ #057: Allow \ in the first position for absolute path
*****************************************************************************/
#include "../stdafx.h"
#include "registry.h"
#if _MSC_VER >= 0x0600
#include "shlwapi.h"
#endif
#include "../resource.h"

/****************************************************************************
*                                   makeKey
* Inputs:
*	HKEY root: Root of key
*       const CString & path: Path of the form
*			a\b\c\d...
*	HKEY * key: Result of opening key
* Result: LONG
*       The result of ::RegOpenKey
* Effect: 
*       If the path cannot be opened, tries to back off creating the key
*	one level at a time
****************************************************************************/

static LONG makeKey(HKEY root, const CString & path, HKEY * key)
    {
     LONG result = ::RegOpenKey(root, path, key);
     if(result == ERROR_SUCCESS)
	return result;

     // We have a path of the form a\b\c\d  
     // But apparently a/b/c doesn't exist

     int i = path.ReverseFind(_T('\\'));
     if(i == -1)
	return result;  // well, we lose

     CString s;
     s = path.Left(i);

     HKEY newkey;
     result = makeKey(root, s, &newkey);
     if(result != ERROR_SUCCESS)
	return result;

     // OK, we now have created a\b\c
     CString v;
     v = path.Right(path.GetLength() - i - 1);
     DWORD disp;

     result = ::RegCreateKeyEx(newkey, 
     				v,
				0, NULL,
				REG_OPTION_NON_VOLATILE,
				KEY_ALL_ACCESS,
				NULL,
				key,
				&disp);
     ::RegCloseKey(newkey); // no longer needed
     return result;
    }

/****************************************************************************
*                                  makePath
* Inputs:
*       CString & path: Existing path, modified if ncessary
*	const CString & var: Value or subpath
*	CString & name: Place to put name
* Result: void
*       
* Effect: 
*       Takes a path of the form
*		\this\that
*	and a variable name 'var' of the form
*		whatever\substring
*	and updates 'path' so that it is
*		\this\that\whatever
*	and updates 'name' so that it is
*		substring
****************************************************************************/

static void makePath(CString & path, const CString & var, CString & name)           // REQ #057
    {
     // locate the rightmost \ of the 'var'.  If there isn't one,
     // we simply copy var to name...
     if(var.GetAt(0) != _T('\\'))                                   // REQ #057
	{ /* local path */                                          // REQ #057
	 path.LoadString(IDS_PROGRAM_ROOT);							// REQ #057
	} /* local path */                                          // REQ #057
     else                                                           // REQ #057
	{ /* absolute path */                                       // REQ #057
	 path = _T("");                                             // REQ #057
	} /* absolute path */                                       // REQ #057

     int i = var.ReverseFind(_T('\\'));

     if(path.GetLength() > 0)                                       // REQ #057
	{ /* append */                                              // REQ #057
	 // append the prefix of the var to the path, leaving only the name
	 if(path[path.GetLength() - 1] != _T('\\'))
	    path += _T("\\");
	 path += var.Left(i);                                       // REQ #057
	 name = var.Right(var.GetLength() - i - 1);                 // REQ #057
	} /* append */                                              // REQ #057
     else                                                           // REQ #057
	{ /* no append */                                           // REQ #057
	 path += var.Mid(1).Left(i - 1);                            // REQ #057
	 name = var.Right(var.GetLength() - i - 1);
	} /* no append */                                           // REQ #057
    }

/****************************************************************************
*                              GetRegistryString
* Inputs:
*	HKEY root: HKEY_CURRENT_USER, etc.
*       const CString &var: Name of variable
*	CString &value: place to put value
* Result: BOOL
*       TRUE if registry key found, &val is updated
*	FALSE if registry key not found, &val is not modified
* Effect: 
*       Retrieves the key based on 
*	root\IDS_PROGRAM_ROOT\var
* Notes:
*	This presumes the value is a text string (SZ_TEXT)
****************************************************************************/

BOOL GetRegistryString(HKEY root, const CString &var, CString & val)
    {
     CString path;
     CString name;
     makePath(path, var, name);

	 if (path.Right(1) == "\\" || path.Right(1) == "/")
		 path = path.Left(path.GetLength()-1);
	 
     HKEY key;
     LONG result = ::RegOpenKey(root, path, &key);
     if(result != ERROR_SUCCESS)
	{ /* not found */
	 ::SetLastError(result);
	 return FALSE;
	} /* not found */
     TCHAR buffer[256];
     DWORD len = sizeof(buffer)/sizeof(TCHAR);
     DWORD type;

     result = ::RegQueryValueEx(key, name, 0, &type, (LPBYTE)buffer, &len);
     ::RegCloseKey(key);

     if(result != ERROR_SUCCESS)
	{ /* error */
	 ::SetLastError(result);
	 return FALSE;
	} /* error */

     if(type != REG_SZ)
	return FALSE;

     val = buffer;

     return TRUE;
    }

/****************************************************************************
*                              GetRegistryString
* Inputs:
*       HKEY root: Root key
*	const CString & path: Path value
*	const CString & var: Variable name
*	CString & val: Place to put value
* Result: BOOL
*       TRUE if successful
*	FALSE if error (::GetLastError gives error result)
* Effect: 
*       Reads the Registry value from the specified path/var
****************************************************************************/

BOOL GetRegistryString(HKEY root, const CString & path, const CString & var, CString & val)
   {
    CString s;
    s = path;
    s += _T("\\");
    s += var;
    return GetRegistryString(root, s, val);
   } // GetRegistryString

/****************************************************************************
*                              GetRegistryString
* Inputs:
*       HKEY root: Root key
*	const CString & path: Path value
*	UINT var: Variable name, as string ID
*	CString & val: Place to put value
* Result: BOOL
*       TRUE if successful
*	FALSE if error (::GetLastError gives error result)
* Effect: 
*       Reads the Registry value from the specified path/var
****************************************************************************/

BOOL GetRegistryString(HKEY root, const CString & path, UINT var, CString & val)
   {
    CString s;
    s = path;
    s += _T("\\");
    CString t;
    t.LoadString(var);
    s += t;
    return GetRegistryString(root, s, val);
   } // GetRegistryString

/****************************************************************************
*			      DeleteRegistryValue
* Inputs:
*       HKEY root: Root of path
*	const CString &var: Name of variable
* Result: BOOL
*       TRUE if item was deleted, or did not exist
*	FALSE if item was not deleted
* Effect: 
*       Deletes the variable named
****************************************************************************/

BOOL DeleteRegistryValue(HKEY root, const CString & var)
    {
     CString path;
     CString name;
     makePath(path, var, name);

     HKEY key;
     LONG result = ::RegOpenKey(root, path, &key);
     if(result != ERROR_SUCCESS)
	return FALSE; // couldn't find it

     ::RegDeleteValue(key, name);
     ::RegCloseKey(key);
     return TRUE; // TODO: error detection
    }

/****************************************************************************
*                              DeleteRegistryKey
* Inputs:
*       HKEY root: Root of path
*	const CString & key: Key name to delete
* Result: BOOL
*       TRUE if successful, or the key was not found (postcondition: key 
*                                          will not exist!)
*	FALSE if error
* Effect: 
*       Deletes the key and all its subkeys
****************************************************************************/

BOOL DeleteRegistryKey(HKEY root, const CString & keyname)
   {
    CString path;
    CString name;
    makePath(path, keyname, name);

    HKEY key;
    LONG result = ::RegOpenKey(root, path, &key);
    if(result == ERROR_FILE_NOT_FOUND)
       return TRUE; // couldn't find it, so it is deleted already
    if(result != ERROR_SUCCESS)
       return FALSE; 

#if _MSC_VER >= 0x0600
    SHDeleteKey(key, name); // delete key and all subkeys
#else
	ASSERT(FALSE); // compiled with wrong compiler
#endif
    return TRUE; // TODO: error detection
   } // DeleteRegistryKey

/****************************************************************************
*                               GetRegistryInt
* Inputs:
*	HKEY  root: root of path
*       const CString &var: Name of variable
*	DWORD &val: Place to put value
* Result: BOOL
*       TRUE if registry key found, &val is updated
*	FALSE if registry key not found, &val is not modified
* Effect: 
*       Retrieves the key based on 
*	root\IDS_PROGRAM_ROOT\var
* Notes:
*	This presumes the value is a 32-bit value
****************************************************************************/

BOOL GetRegistryInt(HKEY root, const CString &var, DWORD & val)
    {
     CString path;
     CString name;
     makePath(path, var, name);

     HKEY key;
     LONG result = ::RegOpenKey(root, path, &key);
     if(result != ERROR_SUCCESS)
	{ /* failed */
	 ::SetLastError(result);
	 return FALSE;
	} /* failed */

     DWORD buffer;
     DWORD len =  sizeof(buffer);
     DWORD type;

     result = ::RegQueryValueEx(key, name, 0, &type, (LPBYTE)&buffer, &len);
     ::RegCloseKey(key);

     if(result != ERROR_SUCCESS)
	 return FALSE;

     if(type != REG_DWORD)
	 return FALSE;

     val = buffer;

     return TRUE;
    }

/****************************************************************************
*                               GetRegistryInt
* Inputs:
*	HKEY  root: root of path
*       const CString &path: path to variable
*	const CString &var: rest of path to variable (may be in path form)
*	DWORD &val: Place to put value
* Result: BOOL
*       TRUE if successful, value is changed
*	FALSE in unsuccessful, value is unchanged
****************************************************************************/

BOOL GetRegistryInt(HKEY root, const CString & path, const CString & var, DWORD & val)
   {
    CString s;
    s = path;
    s += _T("\\");
    s += var;
    return GetRegistryInt(root, s, val);
   } // GetRegistryInt

/****************************************************************************
*                               GetRegistryInt
* Inputs:
*	HKEY  root: root of path
*       const CString &path: path to variable
*	UINT var: rest of path to variable expressed as a stringtable ID
*	DWORD &val: Place to put value
* Result: BOOL
*       TRUE if successful, value is changed
*	FALSE in unsuccessful, value is unchanged
****************************************************************************/

BOOL GetRegistryInt(HKEY root, const CString & path, UINT var, DWORD & val)
   {
    CString s;
    s = path;
    s += _T("\\");
    CString t;
    t.LoadString(var);
    s += t;
    return GetRegistryInt(root, s, val);
   } // GetRegistryInt

/****************************************************************************
*                            GetRegistryDWordArray
* Inputs:
*	HKEY  root: root of path
*       const CString &var: Name of variable
* Result: CDWordArray
*       The array of data
*	NULL if there is an error
* Effect: 
*       Allocates a DWORD array.  
* Notes:
*	The caller is responsible for deleting the result
****************************************************************************/

CDWordArray * GetRegistryDWordArray(HKEY root, const CString &var)
    {
     CString path;
     CString name;
     makePath(path, var, name);
     
     HKEY key;
     LONG result = ::RegOpenKey(root, path, &key);
     if(result != ERROR_SUCCESS)
	{ /* failed */
	 ::SetLastError(result);
	 return NULL;
	} /* failed */

     DWORD len;
     DWORD type;

     result = ::RegQueryValueEx(key, name, 0, &type, NULL, &len);

     if(result != ERROR_SUCCESS)
        { /* failed */
	 ::RegCloseKey(key);
	 return NULL;
	} /* failed */

     CDWordArray * data = new CDWordArray;
     DWORD count = len / sizeof(DWORD);

     data->SetSize((int)count); // preallocate the array data

     result = ::RegQueryValueEx(key, name, 0, &type, (LPBYTE)&(*data)[0], &len);

     if(result != ERROR_SUCCESS)
        { /* failed */
	 ::RegCloseKey(key);
	 delete data;
	 return NULL;
	} /* failed */

     ::RegCloseKey(key);

     return data;
    }

/****************************************************************************
*                            SetRegistryDWordArray
* Inputs:
*	HKEY  root: root of path
*       const CString &var: Name of variable
*	CDWordArray & data: Data to write
* Result: BOOL
*       TRUE if successful
*	FALSE if error
* Effect: 
*       Writes the data for the key
****************************************************************************/

BOOL SetRegistryDWordArray(HKEY root, const CString & var, CDWordArray & data)
    {
     CString path;
     CString name;
     makePath(path, var, name);

     HKEY key;
     LONG result = makeKey(root,
     			        path,
			        &key);
     if(result != ERROR_SUCCESS)
	return FALSE;
     
     result = ::RegSetValueEx(key, name, 0, REG_BINARY, (LPBYTE)&(data[0]), 
     				data.GetSize() * sizeof(DWORD));
     ::RegCloseKey(key);
     return result == ERROR_SUCCESS;

    }

/****************************************************************************
*                              SetRegistryString
* Inputs:
*	HKEY root: root of search
*       const CString & var: Name of variable
*	CString & val: Value to write
* Result: BOOL
*       TRUE if registry key set
*	FALSE if registry key not set
* Effect: 
*       Retrieves the key based on 
*	root\IDS_PROGRAM_ROOT\var
* Notes:
*	This presumes the value is a string
****************************************************************************/

BOOL SetRegistryString(HKEY root, const CString & var, const CString & val)
    {
     CString path;
     CString name;
     makePath(path, var, name);

     HKEY key;
     DWORD disp;
     LONG result = ::RegCreateKeyEx(root,
     				    path,
				    0, NULL, 
				    REG_OPTION_NON_VOLATILE,
				    KEY_ALL_ACCESS,
				    NULL,
				    &key,
				    &disp);
     if(result != ERROR_SUCCESS)
	return FALSE;

     result = ::RegSetValueEx(key, name, 0, REG_SZ, (LPBYTE)(LPCTSTR)val, lstrlen(val) + 1);// REQ #137
     ::RegCloseKey(key);

     if(result != ERROR_SUCCESS)
	 return FALSE;

     return TRUE;
    }

/****************************************************************************
*                              SetRegistryString
* Inputs:
*       HKEY root: Root key
*	const CString & path: Path to variable
*	const CString & var: Variable name
*	const CString & val: Value to set
* Result: BOOL
*       TRUE if successful
*	FALSE if error
* Effect: 
*       Writes the Cstring value to the Registry
****************************************************************************/

BOOL SetRegistryString(HKEY root, const CString & path, const CString & var, const CString & val)
   {
    CString s;
    s = path;
    s += _T("\\");
    s += var;
    return SetRegistryString(root, s, val);
   } // SetRegistryString

/****************************************************************************
*                              SetRegistryString
* Inputs:
*       HKEY root: Root key
*	const CString & path: Path to variable
*	const CString & var: Variable name
*	UINT val: Value to set, expressed as string table ID
* Result: BOOL
*       TRUE if successful
*	FALSE if error
* Effect: 
*       Writes the Cstring value to the Registry
****************************************************************************/

BOOL SetRegistryString(HKEY root, const CString & path, UINT var, const CString & val)
   {
    CString s;
    s = path;
    s += _T("\\");
    CString t;
    t.LoadString(var);
    s += t;
    return SetRegistryString(root, s, val);
   } // SetRegistryString

/****************************************************************************
*                              SetRegistryInt
*	HKEY root : root of search
*       const CString var: Name of variable, including possibly path info
*	DWORD val: Value to set
* Result: BOOL
*       TRUE if registry key set
*	FALSE if registry key not set
* Effect: 
*       Retrieves the key based on 
*	root\IDS_PROGRAM_ROOT\var
* Notes:
*	This presumes the value is a string
****************************************************************************/

BOOL SetRegistryInt(HKEY root, const CString & var, DWORD val)
    {
     CString path;
     CString name;

     makePath(path, var, name);

     HKEY key;
     DWORD disp;
     LONG result = ::RegCreateKeyEx(root,
     			            path,
				    0, NULL, 
				    REG_OPTION_NON_VOLATILE,
				    KEY_ALL_ACCESS,
				    NULL,
				    &key,
				    &disp);
     if(result != ERROR_SUCCESS)
	return FALSE;

     result = ::RegSetValueEx(key, name, 0, REG_DWORD, (LPBYTE)&val, sizeof(DWORD));
     ::RegCloseKey(key);

     if(result != ERROR_SUCCESS)
	 return FALSE;

     return TRUE;
    }

/****************************************************************************
*                               SetRegistryInt
* Inputs:
*       HKEY root:
*	const CString & path: initial path to the variable
*	const CString & var: rest of the path to the variable
*	DWORD val: value to write
* Result: BOOL
*       TRUE if successful
*	FALSE if error
* Effect: 
*       Writes the data to the Registry
****************************************************************************/

BOOL SetRegistryInt(HKEY root, const CString & path, const CString & var, DWORD val)
   {
    CString s = path;
    s += _T("\\");
    s += var;
    return SetRegistryInt(root, s, val);
   } // SetRegistryInt

/****************************************************************************
*                               SetRegistryInt
* Inputs:
*       HKEY root:
*	const CString & path: initial path to the variable
*	UINT var: rest of the path to the variable, as string ID
*	DWORD val: value to write
* Result: BOOL
*       TRUE if successful
*	FALSE if error
* Effect: 
*       Writes the data to the Registry
****************************************************************************/

BOOL SetRegistryInt(HKEY root, const CString & path, UINT var, DWORD val)
   {
    CString s = path;
    s += _T("\\");
    CString t;
    t.LoadString(var);
    s += t;
    return SetRegistryInt(root, s, val);
   } // SetRegistryInt

/****************************************************************************
*                               SetRegistryGUID
* Inputs:
*       HKEY root:
*	const CString &var: Name to store it under
*	const GUID & val: Value to store
* Result: BOOL
*       TRUE if successful, FALSE if error
* Effect: 
*       Retrieves the key based on
*	root\IDS_PROGRAM_ROOT\var
****************************************************************************/

BOOL SetRegistryGUID(HKEY root, const CString & var, const GUID & val)
    {
     CString path;
     CString name;
     makePath(path, var, name);

     HKEY key;
     DWORD disp;
     LONG result = ::RegCreateKeyEx(root,
				    path,
				    0, NULL,
				    REG_OPTION_NON_VOLATILE,
				    KEY_ALL_ACCESS,
				    NULL,
				    &key,
				    &disp);
     if(result != ERROR_SUCCESS)
	return FALSE;

     result = ::RegSetValueEx(key, name, 0, REG_BINARY, (LPBYTE)&val, sizeof(GUID));
     ::RegCloseKey(key);

     if(result != ERROR_SUCCESS)
	return FALSE;

     return TRUE;
    } // SetRegistryGUID

/****************************************************************************
*                               GetRegistryGUID
* Inputs:
*       HKEY root: root of key
*	const CString & Var: Name to store it under
*	GUID & val: Place to store result
* Result: BOOL
*       TRUE if successful, FALSE if error
* Effect: 
*       Retrieves the key based on
*	root\IDS_PROGRAM_ROOT\var
****************************************************************************/

BOOL GetRegistryGUID(HKEY root, const CString & var, GUID & val)
    {
     CString path;
     CString name;
     makePath(path, var, name);

     HKEY key;
     LONG result = ::RegOpenKey(root, path, &key);
     if(result != ERROR_SUCCESS)
	return FALSE;

     DWORD type;
     DWORD len = sizeof(GUID);
     result = ::RegQueryValueEx(key, name, 0, &type, (LPBYTE)&val, &len);

     if(result != ERROR_SUCCESS)
	return FALSE;

     if(type != REG_BINARY)
	return FALSE;

     return TRUE;
    } // GetRegistryGUID

/****************************************************************************
*                              EnumRegistryKeys
* Inputs:
*       HKEY root: Root of search
*	const CString & group: Name of group key
* Result: CStringArray *
*       Array of key names, NULL if no keys found
* Effect: 
*       Enumerates the keys
****************************************************************************/

CStringArray * EnumRegistryKeys(HKEY root, const CString & group)
    {
     CString path;

     CStringArray * keys;
     TCHAR itemName[512];

     path.LoadString(IDS_PROGRAM_ROOT);

     path += _T("\\");
     path += group;


     HKEY key;

     LONG result = makeKey(root, path, &key);
     if(result != ERROR_SUCCESS)
	return NULL;

     keys = new CStringArray;
     DWORD i = 0;
     while(TRUE) //lint -e716
        { /* loop */
	 result = ::RegEnumKey(key, i, itemName, sizeof(itemName)/sizeof(TCHAR));
	 if(result != ERROR_SUCCESS)
	    break;
         // we have a valid key name
	 keys->SetAtGrow(i, itemName);
	 i++;
	} /* loop */

     ::RegCloseKey(key);
     return keys;
    }

/****************************************************************************
*                              EnumRegistryValues
* Inputs:
*       HKEY root: Root of search
*	const CString & group: Name of value group key
* Result: CStringArray *
*       Array of value names, NULL if no keys found
* Effect: 
*       Enumerates the keys
* Notes:
*	The caller is responsible for freeing the array
****************************************************************************/

CStringArray * EnumRegistryValues(HKEY root, const CString & group)
    {
     CString path;

     CStringArray * keys;
     TCHAR itemName[512];

	 path.LoadString(IDS_PROGRAM_ROOT);

     path += _T("\\");
     path += group;


     HKEY key;

     LONG result = makeKey(root, path, &key);
     if(result != ERROR_SUCCESS)
	return NULL;

     keys = new CStringArray;
     DWORD i = 0;
     while(TRUE)
        { /* loop */
	 DWORD length = sizeof(itemName)/sizeof(TCHAR);
	 result = ::RegEnumValue(key, // key selection
	 			      i,   // which key
				      itemName, // place to put value name
				      &length,  // in: length of buffer
				      	        // out: length of name
				      NULL, 	// reserved, NULL
				      NULL, 	// place to put type
				      NULL, 	// place to put value
				      NULL);	// place to put value length
	 if(result != ERROR_SUCCESS)
	    break;
         // we have a valid key name
	 keys->SetAtGrow(i, itemName);
	 i++;
	} /* loop */

     ::RegCloseKey(key);
     return keys;
    }

/****************************************************************************
*                               GetRegistryKey
* Inputs:
*       HKEY root: Root of key
*	const CString & name: Name of key to open
*	HKEY &key: Place to put open key
* Result: BOOL
*       TRUE if key successfully found, key value is valid
*	FALSE if key not found key value undefined
* Effect: 
*       Opens a registry key based on the standard path. It is the
*	responsibility of the caller to close this key.
*	If the key does not exist, the path to it is created
* Notes:
*	This is used when it is necessary to iterate through subkeys
****************************************************************************/

BOOL GetRegistryKey(HKEY root, const CString & keyname, HKEY & key)
   {
    CString path;
    CString name;
    makePath(path, keyname, name);

    path += _T("\\");
    path += name;
    LONG result = makeKey(root, path, &key);

    return result == ERROR_SUCCESS;
   } // GetRegistryKey

/****************************************************************************
*                               FindRegistryKey
* Inputs:
*       HKEY root: Root of key
*	const CString & name: Name of key to open
*	HKEY &key: Place to put open key
* Result: BOOL
*       TRUE if the path exists; key is the open key to that path
*	FALSE if the path does not exist; key is unmodified
* Effect: 
*       Unlike GetRegistryKey, this does not attempt to create the key
****************************************************************************/

BOOL FindRegistryKey(HKEY root, const CString & keyname, HKEY & key)
   {
    CString path;
    CString name;
    makePath(path, keyname, name);                                  // REQ #057

    path += _T("\\");
    path += name;

    LONG result = ::RegOpenKey(root, path, &key);
    return (result == ERROR_SUCCESS);
   } // FindRegistryKey
